package com.boot.security.service.database.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.boot.persistence.service.association.AssociationServiceImpl;
import com.boot.security.mapper.AccessMapper;
import com.boot.security.mapper.UserAccessMapper;
import com.boot.security.mapper.UserMapper;
import com.boot.security.model.po.Access;
import com.boot.security.model.po.User;
import com.boot.security.model.po.UserAccess;
import com.boot.security.service.authentication.TokenService;
import com.boot.security.service.database.UserAccessService;
import com.boot.web.exception.GlobalException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.validation.ConstraintViolationException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 霜寒 <1621856595@qq.com>
 * @date 2021/03/07 15:07
 **/
@Service
public class UserAccessServiceImpl extends AssociationServiceImpl<Long, Long, Long, UserAccess, User, Access,
        UserAccessMapper, UserMapper, AccessMapper> implements UserAccessService {

    TokenService tokenService;

    public UserAccessServiceImpl(UserAccessMapper mapper, UserMapper userMapper, AccessMapper accessMapper, TokenService tokenService) {
        super(mapper, userMapper, accessMapper);
        this.tokenService = tokenService;
    }

    @Override
    public Set<Access> searchUserAllAccesses(Long id) {
        try {
            List<UserAccess> list = super.list(Wrappers.query(UserAccess.builder().userId(id).build()));
            List<Long> accessIds = list.stream().map(UserAccess::getAccessId).collect(Collectors.toList());
            if (accessIds.size() == 0) {
                return Collections.emptySet();
            } else {
                Collection<Access> accessList = getNMapper().selectBatchIds(accessIds);
                return new HashSet<>(accessList);
            }
        } catch (GlobalException | ConstraintViolationException ignore) {
            return Collections.emptySet();
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean save(UserAccess entity) {
        List<Long> accessIds = mapper
                .selectList(Wrappers.query(UserAccess.builder().userId(entity.getUserId()).build()))
                .stream().map(UserAccess::getAccessId).collect(Collectors.toList());
        Set<Long> insertIds = new HashSet<>();
        save(entity.getUserId(), entity.getAccessId(), new HashSet<>(accessIds), insertIds);
        if (!CollectionUtils.isEmpty(insertIds)) {
            tokenService.invalid(entity.getUserId());
            return true;
        } else {
            return false;
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean saveBatch(Collection<UserAccess> entityList) {
        entityList.forEach(this::save);
        return true;
    }

    public void save(Long userId, Long accessId, Set<Long> existAccesses, Set<Long> insertIds) {
        if (!existAccesses.contains(accessId)) {
            UserAccess model = UserAccess.builder()
                    .userId(userId)
                    .accessId(accessId)
                    .build();
            mapper.insert(model);
            existAccesses.add(accessId);
            insertIds.add(model.getId());
            List<Access> children = getNMapper()
                    .selectList(Wrappers.query(Access.builder()
                            .parent(accessId)
                            .build()));
            if (children != null && children.size() > 0) {
                children.forEach(access -> save(userId, access.getId(), existAccesses, insertIds));
            }
        }
    }

    @Override
    public boolean removeById(Long id) {
        UserAccess userAccess = mapper.selectById(id);
        if (userAccess == null) {
            return false;
        } else {
            List<UserAccess> exist = mapper
                    .selectList(Wrappers.query(UserAccess.builder().userId(userAccess.getUserId()).build()));
            Access access = getNMapper().selectById(userAccess.getAccessId());
            Set<Long> deleteIds = new HashSet<>();
            delete(userAccess.getUserId(), access, new HashSet<>(exist), deleteIds);
            if (!CollectionUtils.isEmpty(deleteIds)) {
                tokenService.invalid(userAccess.getUserId());
                return true;
            } else {
                return false;
            }
        }
    }

    @Override
    public boolean removeByIds(Collection<? extends Long> idList) {
        idList.forEach(this::removeById);
        return true;
    }

    public void delete(Long userId, Access access, Set<UserAccess> existAccesses, Set<Long> deleteIds) {
        Set<UserAccess> filter = existAccesses.stream().filter(model -> model.getUserId().equals(userId)
                && model.getAccessId().equals(access.getId())).collect(Collectors.toSet());
        if (filter.size() > 0) {
            Set<Long> idList = filter.stream().map(UserAccess::getId).collect(Collectors.toSet());
            super.removeByIds(idList);
            existAccesses.removeAll(filter);
            deleteIds.addAll(idList);
            Access parent = getNMapper().selectById(access.getParent());
            if (parent != null) {
                delete(userId, parent, existAccesses, deleteIds);
            }
        }
    }

    @Override
    public UserAccess createModel() {
        return new UserAccess();
    }

    @Override
    public User createMModel() {
        return new User();
    }

    @Override
    public Access createNModel() {
        return new Access();
    }
}
